package com.ruoyi.interceptor;

import com.ruoyi.app.utils.ApiTokenServiceUtil;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.app.domain.vo.UserLoginVO;
import com.ruoyi.utils.ApiThreadUtils;
import com.ruoyi.utils.IpUtil;
import io.jsonwebtoken.MalformedJwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Slf4j
@Component
public class ApiTokenInterceptor implements HandlerInterceptor {

    @Value("${api.token.header}")
    private String TOKEN_HEADER;

    @Value("${api.token.excludes}")
    private String EXCLUDES;

    @Value("${api.token.timeout}")
    private Long TIMEOUT;

    @Autowired
    private ApiTokenServiceUtil apiTokenServiceUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        try {
            // 请求IP地址
            log.info("请求ip: {}, url: {}", IpUtil.getIpAddr(request), request.getRequestURI());

            // 请求开始时间
            ApiThreadUtils.setRequestStartTime(System.currentTimeMillis());

            // 判断路径是否在排除列表中
            if (this.authPathExclude(request.getRequestURI())) {
                return true;
            }

            String token = request.getHeader(TOKEN_HEADER);
            if (token == null || token.trim().isEmpty()) {
                response.setStatus(HttpStatus.UNAUTHORIZED);
                return false;
            }

            // 获取用户信息
            UserLoginVO user = apiTokenServiceUtil.getLoginUser(token);
            if (user == null) {
                response.setStatus(HttpStatus.UNAUTHORIZED);
                return false;
            }

            // 验证令牌是否有效
            apiTokenServiceUtil.verifyToken(user);

            // 设置当前线程的用户信息
            ApiThreadUtils.setUser(user);
            return true;
        } catch (MalformedJwtException e) {
            log.error("无效令牌: ip={}, url={}", IpUtil.getIpAddr(request), request.getRequestURI(), e);
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.setCharacterEncoding("UTF-8");
            response.getWriter().write("无效令牌");
            return false;
        } catch (Exception e) {
            log.error("请求处理过程中发生异常: ip={}, url={}", IpUtil.getIpAddr(request), request.getRequestURI(), e);
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            response.setCharacterEncoding("UTF-8");
            response.getWriter().write("服务器内部错误");
            return false;
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        // 请求处理时长
        long handleTime = System.currentTimeMillis() - ApiThreadUtils.getRequestStartTime();

        // 判断请求处理时间是否正常
        if (handleTime > TIMEOUT) {
            log.warn("请求处理超时: {}ms, url: {}, ip: {}", handleTime , request.getRequestURI(), IpUtil.getIpAddr(request));
        } else{
            // 打印当前请求处理时长
            log.info("请求处理时长: {}ms, url: {}, ip: {}", handleTime , request.getRequestURI(), IpUtil.getIpAddr(request));
        }

        // 删除当前线程的用户信息
        ApiThreadUtils.removeUser();

        // 删除当前线程的请求开始时间
        ApiThreadUtils.removeRequestStartTime();
    }

    /**
     * 判断请求路径是否在排除列表中
     */
    private boolean authPathExclude(String path) {
        if (!EXCLUDES.isEmpty()){
            String[] url = EXCLUDES.split(",");
            List<String> data = new ArrayList<String>(Arrays.asList(url));
            for (String exclude : data){
                Pattern p = Pattern.compile("^" + exclude);
                Matcher m = p.matcher(path);
                if (m.find()) {
                    return true;
                }
            }
        }
        return false;
    }
}
